package dev.ece.util.lib;

import java.nio.FloatBuffer;

import com.bulletphysics.linearmath.Transform;

import dev.ece.util.GLUtils;
import dev.ece.util.Matrix;
import dev.ece.util.tools.Screen;

/**
 * 4x4矩阵
 * @author 潜行的青衣
 * @see
 */
public class Matrix4f {
	
	protected float[] matrix;
	
	public Matrix4f() {
		this.matrix = new float[16];
	}
	
	public Matrix4f(float[] matrix) {
		if(matrix == null) {
			throw new NullPointerException("matrix can not be null");
		}
		if(matrix.length != 16) {
			throw new IllegalArgumentException("matrix length must be 16");
		}
		this.matrix = matrix;
	}
	
	public void from(Transform transform) {
		transform.getOpenGLMatrix(getMatrix());
	}
	
	public void to(Transform transform) {
		transform.setFromOpenGLMatrix(getMatrix());
	}
	
	public void mul(Matrix4f matrix) {
		Matrix.multiplyMM(this.matrix, 0, this.matrix, 0, matrix.matrix, 0);
	}
	
	public void mul(Matrix4f left, Matrix4f right) {
		Matrix.multiplyMM(this.matrix, 0, left.matrix, 0, right.matrix, 0);
	}
	
	public Vector3f transform(Vector3f vec) {
		Matrix.transform(vec.getVector(), 0, this.matrix, 0, vec.getVector(), 0);
		return vec;
	}
	
	public void transpose() {
		float[] matrix = new float[16];
		Matrix.transposeM(matrix, 0, this.matrix, 0);
		this.matrix = matrix;
	}
	
	public void transpose(Matrix4f matrix) {
		Matrix.transposeM(this.matrix, 0, matrix.matrix, 0);
	}
	
	public Matrix4f invert() {
		Matrix.invertM(this.matrix, 0, this.matrix, 0);
		return this;
	}
	
	public Matrix4f invert(Matrix4f matrix) {
		Matrix.invertM(this.matrix, 0, matrix.matrix, 0);
		return this;
	}
	
	public void ortho(float left, float right, float bottom, float top,
	        float near, float far) {
		Matrix.orthoM(this.matrix, 0, left, right, bottom, top, near, far);
	}
	
	public void frustum(float left, float right, float bottom, float top,
            float near, float far) {
		Matrix.frustumM(this.matrix, 0, left, right, bottom, top, near, far);
	}
	
	public void perspective(float fovy, float aspect, float zNear, float zFar) {
		Matrix.perspectiveM(this.matrix, 0, fovy, aspect, zNear, zFar);
	}
	
	public Matrix4f identity() {
		Matrix.setIdentityM(this.matrix, 0);
		return this;
	}
	
	public float[] getMatrix() {
		return matrix;
	}

	public Matrix4f shadow() {
		for (int i=0 ; i<16 ; i++) {
            this.matrix[i] = 0;
        }
		this.matrix[0] = 0.5f;
		this.matrix[5] = 0.5f;
		this.matrix[10] = 0.5f;
		
		this.matrix[12] = 0.5f;
		this.matrix[13] = 0.5f;
		this.matrix[14] = 0.5f;
		
		this.matrix[15] = 1f;
		return this;
	}
	
	public void lookAt(float eyeX, float eyeY, float eyeZ,
            float centerX, float centerY, float centerZ, float upX, float upY,
            float upZ) {
		Matrix.setLookAtM(this.matrix, 0, eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
	}
	
	public void lookAt(Vector3f eye, Vector3f center, Vector3f up) {
		this.lookAt(
				eye.getX(), eye.getY(), eye.getZ(), 
				center.getX(), center.getY(), center.getZ(), 
				up.getX(), up.getY(), up.getZ());
	}
	
	public void translate(float x, float y, float z) {
		Matrix.translateM(matrix, 0, x, y, z);
	}
	
	public void translate(Vector3f vec) {
		this.translate(vec.getX(), vec.getY(), vec.getZ());
	}
	
	public void setTranslate(Vector3f vec) {
		this.setTranslate(vec.getX(), vec.getY(), vec.getZ());
	}
	
	public void setTranslate(float x, float y, float z) {
		this.matrix[12] = x;
		this.matrix[13] = y;
		this.matrix[14] = z;
	}
	
	public void rotateQuaternion(float x, float y, float z, float w) {
		float x2 = x * x;
		float y2 = y * y;
		float z2 = z * z;
		float xy = x * y;
		float xz = x * z;
		float yz = y * z;
		float wx = w * x;
		float wy = w * y;
		float wz = w * z;
		this.matrix[0] = 1.0f - 2.0f * (y2 + z2);
		this.matrix[4] = 2.0f * (xy - wz);
		this.matrix[8] = 2.0f * (xz + wy);
		this.matrix[12] = 0.0f;
		
		this.matrix[1] = 2.0f * (xy + wz);
		this.matrix[5] = 1.0f - 2.0f * (x2 + z2);
		this.matrix[9] = 2.0f * (yz - wx);
		this.matrix[13] = 0.0f;
		
		this.matrix[2] = 2.0f * (xz - wy);
		this.matrix[6] = 2.0f * (yz + wx);
		this.matrix[10] = 1.0f - 2.0f * (x2 + y2);
		this.matrix[14] = 0.0f;
		
		this.matrix[3] = 0.0f;
		this.matrix[7] = 0.0f;
		this.matrix[11] = 0.0f;
		this.matrix[15] = 1.0f;
	}
	
	public void rotateQuaternion(Vector4f quaternion) {
		rotateQuaternion(quaternion.getX(), quaternion.getY(), quaternion.getZ(), quaternion.getW());
	}

	public void rotate(float x, float y, float z, float angle) {
		if((Math.abs(x) + Math.abs(y) + Math.abs(z)) > 0) {
			Matrix.rotateM(this.matrix, 0, angle, x, y, z);
		}
	}
	
	public void rotate(Vector4f vec) {
		this.rotate(vec.getX(), vec.getY(), vec.getZ(), vec.getAngle());
	}
	
	public void rotate(Vector3f vec, float angle) {
		this.rotate(vec.getX(), vec.getY(), vec.getZ(), angle);
	}
	
	public void scale(float x, float y, float z) {
		Matrix.scaleM(this.matrix, 0, x, y, z);
	}
	
	public void scale(Vector3f vec) {
		this.scale(vec.getX(), vec.getY(), vec.getZ());
	}
	
	public void store(FloatBuffer buff) {
		buff.put(this.matrix);
	}
	
	public void store3f(FloatBuffer buff) {
		for(int i=0; i<3; i++) {
			for(int j=0; j<3; j++) {
				buff.put(matrix[i * 4 + j]);
			}
		}
	}
	
	public void set(Matrix4f matrix) {
		this.matrix = matrix.clone().matrix;
	}
	
	public Matrix4f clone() {
		return new Matrix4f(this.matrix.clone());
	}
	
	public static Matrix4f interpolate(Matrix4f left, Matrix4f right, float alpha) {
		float leftAlpha = 1.0f - alpha;
		float[] matrix = new float[left.matrix.length];
		for(int i=0; i<matrix.length; i++) {
			matrix[i] = left.matrix[i] * leftAlpha + right.matrix[i] *alpha;
		}
		return new Matrix4f(matrix);
	}
	
	public static Vector3f gluUnProject(float winX, float winY, float winZ, Matrix4f project, Matrix4f model, Screen screen) {
		float[] result = new float[4];
		GLUtils.gluUnProject(winX, screen.getHeight() - winY - 1, winZ, model.matrix, 0, project.matrix, 0, screen.toViewArray(), 0, result, 0);
		for(int i=0; i<3; i++) {
			result[i] /= result[3];
		}
		return new Vector3f(result[0], result[1], result[2]);
	}
	
	public String toString() {
		StringBuilder s = new StringBuilder();
		s.append("(");
		s.append(matrix[0]).append(",").append(matrix[4]).append(",").append(matrix[8]).append(",").append(matrix[12]).append("\n");
		s.append(matrix[1]).append(",").append(matrix[5]).append(",").append(matrix[9]).append(",").append(matrix[13]).append("\n");
		s.append(matrix[2]).append(",").append(matrix[6]).append(",").append(matrix[10]).append(",").append(matrix[14]).append("\n");
		s.append(matrix[3]).append(",").append(matrix[7]).append(",").append(matrix[11]).append(",").append(matrix[15]).append("\n");
		s.append(")");
		return s.toString();
	}
}
